import java.net.*;
import java.io.*;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Serwer 
{
    public static void main(String[] args) throws IOException 
    {
           long czasall1, czasall2;
           long czasallbez1, czasallbez2;
           double czaswykonaniaallbez;
           double czaswykonaniaall;

        GregorianCalendar data=new GregorianCalendar();
        String nazwaPliku = "Serwer_log_"+data.get(Calendar.YEAR)+"_"+data.get(Calendar.MONTH)+"_"+data.get(Calendar.DAY_OF_MONTH)+"_"+data.get(Calendar.HOUR)+"_"+data.get(Calendar.MINUTE)+"_"+data.get(Calendar.SECOND)+".txt";
        PrintWriter zapis = null;
        Zegar zegarSerwer = new Zegar(0);
        
        int czykoniec=0;
        ObjectInputStream ois;
        ObjectOutputStream oos;
        Dane1 daneWejsciowe = new Dane1();
        Dane2 daneWyjsciowe = new Dane2();
        ServerSocket serverSocket = null; 
        try 
        {
            serverSocket = new ServerSocket(4444);
        } catch (IOException e) 
        {
            System.err.println("Nie moge oczekiwac na porcie 4444!");
            System.exit(1);
        }
        while (true)
        {
            Socket clientSocket = null; 
            System.out.println("Jestem Serwer:");
            System.out.println(" 'exit' - wyrzucenie Klienta i ponowne oczekiwanie");
            System.out.println(" 'quit' - zakonczenie pracy");
            System.out.println("Oczekiwanie na polaczenie...");
            try 
            {
                clientSocket = serverSocket.accept();
            } catch (IOException e) 
            {
                System.err.println("Blad accept");
                System.exit(1);
            }
            czasall1=System.currentTimeMillis();
            oos = new ObjectOutputStream(clientSocket.getOutputStream());
            ois = new ObjectInputStream(clientSocket.getInputStream());
            try 
            {
                daneWejsciowe = (Dane1) ois.readObject();
            } 
            catch (ClassNotFoundException ex) 
            {
                Logger.getLogger(Serwer.class.getName()).log(Level.SEVERE, null, ex);
            }
            System.out.println("Server: b: " + daneWejsciowe.b + " d: " + daneWejsciowe.d + " nb: " + daneWejsciowe.nb + " nd: " + daneWejsciowe.nd  + " rb: " + daneWejsciowe.rb + " zakres: " + daneWejsciowe.zakres);
            
            zegarSerwer.setClock(1, daneWejsciowe.zegar);
            zegarSerwer.increment();
            try 
            {
                zapis = new PrintWriter(new FileWriter(nazwaPliku, true));
            } catch (FileNotFoundException ex) {
                Logger.getLogger(Serwer.class.getName()).log(Level.SEVERE, null, ex);
            }
            data=new GregorianCalendar();   
            zapis.println("Data i czas: "+data.get(Calendar.DAY_OF_MONTH)+"."+data.get(Calendar.MONTH)+"."+data.get(Calendar.YEAR)+" godz: "+data.get(Calendar.HOUR)+":"+data.get(Calendar.MINUTE)+":"+data.get(Calendar.SECOND));            
            zapis.println("Zegar: " + zegarSerwer.getLocalMax());
            zapis.println("Odebrałem dane z: " + clientSocket.getInetAddress().toString());            
            zapis.println("Dane: b: " + daneWejsciowe.b + " d: " + daneWejsciowe.d + " nb: " + daneWejsciowe.nb + " nd: " + daneWejsciowe.nd  + " rb: " + daneWejsciowe.rb + " zakres: " + daneWejsciowe.zakres);        

            
            zegarSerwer.increment();
            zapis.println("Zegar: " + zegarSerwer.getLocalMax());
            zapis.println("Rozpoczynam obliczenia");           
            zapis.close();
            
            // OBLICZENIA!!!!!
            
            czasallbez1=System.currentTimeMillis();
            
            double d,b;
            int nd,nb,nw,ne;
            boolean czyok=true;
            double p[];			//wektor sztywności
            double p1[], p2[], p3[], p4[];
            double dx;			//krok po x
            double dy;			//krok po y
            int ka;				//pomocnicza
            double x[];			//wartości x
            double y[];			//wartości y
            double h[][];			//??
            double h1[][], h2[][], h3[][], h4[][];
            int ie[][];			//??
            double xe[];			//??
            double ye[];			//??
            double he[][];	//elementy macierzy lokalnej
            double nksz[];		//funkcje kształtu
            int ii;
            int jj;
            int nw0[];
            double u[];
            double t[];
            double t1[] = null, t2[] = null, t3[] = null, t4[] = null;
            String wynik1 = "", wynik2="", wynik3="", wynik4="";
            double LL[][];
            double D[][];
            double U[][];
            double r[];
            double pomoc2[];
            double maxr;
            double zakres1 = 1;
            double zakres2 = 1;
            double matrix[][];
            double aobl,bobl;
            long czas1[], czas2[];
            double czaswykonania[];
            double blad[];
            int rb;
            double rp[], rp0[], fp[], pp[], ppstare[], qp[], up[], wp[],pomoc1[];

            HashMap<Integer, Double> map = new HashMap<>();

            d=daneWejsciowe.d;
            b=daneWejsciowe.b;
            nd=daneWejsciowe.nd;
            nb=daneWejsciowe.nb;
            zakres1=daneWejsciowe.zakres;
            zakres2=daneWejsciowe.zakres;
            map=daneWejsciowe.map;
            rb=daneWejsciowe.rb;


            nw=nb*nd;               //ilość węzłów
            ne=2*(nb-1)*(nd-1);	//ilość elementów

            h = new double [nw+1][nw+1];
            ie = new int [ne+1][4];
            p = new double [nw+1];
            t = new double [nw+1];
            x = new double [nw+1];
            y = new double [nw+1];
            nw0 = new int [nw+1];
            u = new double [nw+1];
            xe = new double [4];
            ye = new double [4];
            nksz = new double [4];
            he = new double [4][4];

            dx= (d/(nd-1));	//krok po x
            dy= (b/(nb-1));	//krok po y

            for (int i=1;i<=nd;i++)
            {
                for (int j=1;j<=nb;j++)
                {
                    ka=(i-1)*nb+j;
                    x[ka]=(i-1)*dx;
                    y[ka]=(j-1)*dy;
                }		           
            }

            ie[1][1]=1;
            ie[1][2]=nb+1;
            ie[1][3]=nb+2;
            ie[2][1]=1;
            ie[2][2]=nb+2;
            ie[2][3]=2;

            for(int n=3;n<=ne;n=n+2)
            {
                ka=(2*(nb-1));
                if( (n-1) == (ka*((n-1)/ka)) ) 
                    ka=2;
                else
                    ka=1;
                for(int i=1;i<=3;i++)
                    ie[n][i]=ie[n-2][i]+ka;
            }

            for(int n=4;n<=ne;n=n+2)
            {
                ka=(2*(nb-1));
                if( (n-2) == (ka*((n-2)/ka)) ) 
                    ka=2;
                else
                    ka=1;
                for(int i=1;i<=3;i++)
                    ie[n][i]=ie[n-2][i]+ka;
            }


            for (int i=1;i<=nw;i++)
                for (int j=1;j<=nw;j++)
                    h[i][j]=0;


            for(int n=1;n<=ne;n++)
            {
                for(int i=1;i<=3;i++)
                {
                    xe[i]=x[ie[n][i]];
                    ye[i]=y[ie[n][i]];
                }

                double k=1.0;
                double xx=1;
                double yy=1;
                double s=0.5*(xe[1]*(ye[2]-ye[3])+xe[2]*(ye[3]-ye[1])+xe[3]*(ye[1]-ye[2]));
                nksz[1]=0.5*(xe[2]*(ye[3]-yy)+xe[3]*(yy-ye[2])+xx*(ye[2]-ye[3]))/s;
                nksz[2]=0.5*(xe[3]*(ye[1]-yy)+xe[1]*(yy-ye[3])+xx*(ye[3]-ye[1]))/s;
                nksz[3]=0.5*(xe[1]*(ye[2]-yy)+xe[2]*(yy-ye[1])+xx*(ye[1]-ye[2]))/s;

                he[1][1]=(k/4*s)*((ye[2]-ye[3])*(ye[2]-ye[3])+(xe[3]-xe[2])*(xe[3]-xe[2]));
                he[1][2]=(k/4*s)*((ye[2]-ye[3])*(ye[3]-ye[1])+(xe[3]-xe[2])*(xe[1]-xe[3]));
                he[1][3]=(k/4*s)*((ye[2]-ye[3])*(ye[1]-ye[2])+(xe[3]-xe[2])*(xe[2]-xe[1]));
                he[2][1]=(k/4*s)*((ye[3]-ye[1])*(ye[2]-ye[3])+(xe[1]-xe[3])*(xe[3]-xe[2]));
                he[2][2]=(k/4*s)*((ye[3]-ye[1])*(ye[3]-ye[1])+(xe[1]-xe[3])*(xe[1]-xe[3]));
                he[2][3]=(k/4*s)*((ye[3]-ye[1])*(ye[1]-ye[2])+(xe[1]-xe[3])*(xe[2]-xe[1]));
                he[3][1]=(k/4*s)*((ye[1]-ye[2])*(ye[2]-ye[3])+(xe[2]-xe[1])*(xe[3]-xe[2]));
                he[3][2]=(k/4*s)*((ye[1]-ye[2])*(ye[3]-ye[1])+(xe[2]-xe[1])*(xe[1]-xe[3]));
                he[3][3]=(k/4*s)*((ye[1]-ye[2])*(ye[1]-ye[2])+(xe[2]-xe[1])*(xe[2]-xe[1]));


                for (int i=1;i<=3;i++)
                {
                    ii=ie[n][i];
                    for (int j=1;j<=3;j++)
                    {
                        jj=ie[n][j];
                        h[ii][jj]+=he[i][j];                
                    }
                }		
            }

            for (int i=0;i<=nw;i++)
            {
                t[i]=0;
                p[i]=0;
            }

            double nb1;
            nb1=map.size();
            int warn=1;
            Set<Integer> kluczwar = map.keySet();
            for(Integer k : kluczwar){
                nw0[warn]=k;
                u[warn] = map.get(k);
                warn++;
            }

            for(int i=1;i<=nb1;i++)
            {
                for(int k=1;k<=nw;k++)
                {
                    h[nw0[i]][k]=0;
                }
                p[nw0[i]]=u[i];
                h[nw0[i]][nw0[i]]=1;
            }           

            czas1 = new long [4];
            czas2 = new long [4];
            czaswykonania = new double [4];
            blad = new double [4];
            for(int i=0;i<4;i++)
                blad[i]=0;

            System.gc();
            if(rb==1)
            {
                h1 = new double [nw+1][nw+1];
                p1 = new double [nw+1];
                t1 = new double [nw+1];
                for(int i=1;i<=nw;i++)
                {
                    p1[i]=p[i];
                    t1[i]=0;    
                    for(int j=1;j<=nw;j++)
                    {
                        h1[i][j]=h[i][j];
                    }            
                }               
                System.out.println("Metoda Gaussa\n");
                int JJ;
                double XM,SUM;
                czas1[0]=System.currentTimeMillis();
                for (int J = 1 ; J <= (nw-1) ; J++ )
                {
                    for ( JJ = J+1 ; JJ <= nw ; JJ++ ) 
                    {	
                        XM = h1[JJ][J]/h1[J][J];
                        for (int I = J ; I <= nw ; I++ ) 
                        {
                            h1[JJ][I] = h1[JJ][I] - h1[J][I]*XM;
                        }
                        p1[JJ] = p1[JJ] - (p1[J]*XM);
                    }
                }
                System.out.println("polowa");
                t1[nw] = p1[nw]/h1[nw][nw];
                for (int J = 1 ; J <= (nw-1) ; J++ ) 
                {
                    JJ = nw-J;
                    SUM = 0.0;
                    for (int I = JJ+1 ; I <= nw ; I++ ) 
                    {
                        SUM = SUM + h1[JJ][I]*t1[I];
                    }
                    t1[JJ] = (p1[JJ] - SUM) / h1[JJ][JJ];
                }
                czas2[0]=System.currentTimeMillis();
                czaswykonania[0]=(czas2[0]-czas1[0]);
                czaswykonania[0]/=1000;
                System.out.println(czaswykonania[0]);
                    blad[0]=0;

                wynik1+="x\t"+"y\t"+"t"+"\n";
                for(int i=0;i<nw;i++)
                    t1[i]=t1[i+1];          
                for(int i=0;i<nw;i++)
                {
                    wynik1+=x[i+1]+"\t"+y[i+1]+"\t"+t1[i]+"\n";
                }    
            }
            System.out.println(wynik1);
            System.gc();

            
            if(rb==2)
            {    
                p2 = new double [nw+1];
                t2 = new double [nw+1];
                for(int i=1;i<=nw;i++)
                {
                    p2[i]=p[i];
                    t2[i]=0;    
                }       
                for (int i=0; i<nw; i++)
                    p2[i]=p[i+1];


                System.out.println("Metoda Gaussa-Seidela\n");
                LL = new double [nw][nw];
                D = new double [nw][nw];
                U = new double [nw][nw];
                r = new double [nw];
                pomoc2 = new double [nw];
                czas1[1]=System.currentTimeMillis();   

                for (int i=0; i<nw; i++)
                    for (int j=0; j<nw; j++)
                    {
                        if (i < j)
                            U[i][j] = h[i+1][j+1];
                        else if (i > j)
                            LL[i][j] = h[i+1][j+1];
                        else 
                            D[i][j] = h[i+1][j+1];
                    }

                // Calculate D^-1
                for (int i=0; i<nw; i++)
                    D[i][i] = 1/D[i][i];

                // Calculate D^-1 * b
                for (int i=0; i<nw; i++)
                    p2[i] *= D[i][i];

                //Calculate D^-1 * L
                    for (int i=0; i<nw; i++)
                        for (int j=0; j<i; j++)
                            LL[i][j] *= D[i][i];

                //Calculate D^-1 * U
                    for (int i=0; i<nw; i++)
                        for (int j=i+1; j<nw; j++)
                            U[i][j] *= D[i][i];

                //nw - ilość wykonania algorytmu
                do {
                    for (int i=0; i<nw; i++) 
                    {
                        t2[i] = p2[i];					// x = D^-1*b -
                        for (int j=0; j<i; j++)
                            t2[i] -= LL[i][j]*t2[j];		// D^-1*L * x -
                        for (int j=i+1; j<nw; j++)
                            t2[i] -= U[i][j]*t2[j];   // D^-1*U * x
                    }

                    //r=b-Ax
                    for (int i = 0; i < nw; i++)
                        for (int j=0;j<nw;j++)
                            pomoc2[i] += h[i+1][j+1] * t2[j];
                    maxr = Math.abs(r[0]=p[1]-pomoc2[0]);
                    for (int i = 0; i < nw; i++)
                    {
                        r[i]=p[i+1]-pomoc2[i];
                        pomoc2[i]=0;
                        if(maxr<Math.abs(r[i])) maxr=Math.abs(r[i]);
                    }   
                }while(maxr>zakres1);

                czas2[1]=System.currentTimeMillis(); 
                czaswykonania[1]=(czas2[1]-czas1[1]);
                czaswykonania[1]/=1000;
                System.out.println(czaswykonania[1]);
                System.gc();            
                wynik2+="x\t"+"y\t"+"t"+"\n";
                for(int i=0;i<nw;i++)
                {
                    wynik2+=x[i+1]+"\t"+y[i+1]+"\t"+t2[i]+"\n";
                }  
            }

            System.out.println(wynik2);
            System.gc();
            if(rb==3)        
            {  
                t3 = new double [nw+1];
                for(int i=1;i<=nw;i++)
                {
                    t3[i]=0;    
                }   
                System.out.println("Metoda Gaussa-Jordan\n");
                matrix = new double [nw][nw+1];

                //macierz h + p
                for (int i = 0; i < nw ; i++)
                    for (int j = 0; j < nw; j++)
                        matrix[i][j]=h[i+1][j+1];
                for (int i = 0; i < nw; i++)
                    matrix[i][nw]=p[i+1];

                czas1[2]=System.currentTimeMillis(); 

                for (int i = 0; i < nw; i++)
                {
                    for (int j = 0; j < nw; j++)
                    {
                        if(i != j)
                        {
                            aobl = matrix[j][i];
                            bobl = matrix[i][i];
                            for (int k = 0; k < nw+1; k++)
                                matrix[j][k] = matrix[j][k] - (aobl/bobl) * matrix[i][k];
                        }
                    }
                }
                for (int i = 0; i < nw; i++)
                {
                    aobl = matrix[i][i];
                    for (int j = 0; j < nw+1; j++)
                        matrix[i][j] /= aobl;
                }

                czas2[2]=System.currentTimeMillis(); 
                czaswykonania[2]=(czas2[2]-czas1[2]);
                czaswykonania[2]/=1000;
                System.out.println(czaswykonania[2]);

                for (int i = 0; i < nw ; i++)
                    t3[i]=matrix[i][nw];

                wynik3+="x\t"+"y\t"+"t"+"\n";
                for(int i=0;i<nw;i++)
                {
                    wynik3+=x[i+1]+"\t"+y[i+1]+"\t"+t3[i]+"\n";
                }  
            }



            System.out.println(wynik3);
            System.gc();
            if(rb==4)        
            {  
                t4 = new double [nw+1];
                for(int i=1;i<=nw;i++)
                {
                    t4[i]=0;    
                }               
                System.out.println("Metoda kwadratu gradientów sprzężonych\n");
                double alpha, beta, sigmap, ep, rop, ropstare;        
                rp = new double [nw];
                rp0 = new double [nw];
                fp = new double [nw];
                pp = new double [nw];
                ppstare = new double [nw];
                qp = new double [nw];
                up = new double [nw];
                wp = new double [nw];
                pomoc1 = new double [nw];     

                czas1[3]=System.currentTimeMillis(); 

                for (int i = 0; i < nw; i++)
                {
                    rp0[i]=(i+1)*2;
                    rp[i] = p[i+1];		//r1
                    qp[i] = 0;		//r1stare
                    ppstare[i] = 0;		//r2stare
                }
                ropstare = 1;
                rop=0;
                for (int i = 0; i < nw; i++)
                    rop += rp0[i] * rp[i];	

                do {
                    beta=rop/ropstare;

                    //u(k) = r(k) + β(k)q(k)
                    for (int i = 0; i < nw; i++)
                        pomoc1[i]=0;
                    for (int i = 0; i < nw; i++)
                        pomoc1[i] = qp[i] * beta;	
                    for (int i = 0; i < nw; i++)
                        up[i] = rp[i]+pomoc1[i];

                    //p(k) = u(k) + β(k)(q(k) + β(k) p(k–1))
                    for (int i = 0; i < nw; i++)
                        pomoc1[i]=0;
                    for (int i = 0; i < nw; i++)
                        pomoc1[i] = ppstare[i] * beta;	
                    for (int i = 0; i < nw; i++)
                        pomoc1[i] += qp[i];
                    for (int i = 0; i < nw; i++)
                        pomoc1[i] *= beta;
                    for (int i = 0; i < nw; i++)
                        pp[i] = up[i] + pomoc1[i];

                    //w(k) = A*p(k)
                    for (int i = 0; i < nw; i++)
                        wp[i]=0;
                    for (int i = 0; i < nw; i++)
                        for (int j=0;j<nw;j++)
                            wp[i] += h[i+1][j+1] * pp[j];

                    //σ(k) = r0(k)T w(k)
                    sigmap=0;
                    for (int i = 0; i < nw; i++)
                        sigmap+=rp0[i]*wp[i];

                    //α(k) = ρ(k) / σ(k)
                    alpha=0;
                    alpha = rop/sigmap;

                    //q(k+1) = u(k) – alpha*w(k)
                    for (int i = 0; i < nw; i++)
                        pomoc1[i]=0;
                    for (int i = 0; i < nw; i++)
                        pomoc1[i] = alpha * wp[i];	
                    for (int i = 0; i < nw; i++)
                        qp[i]= up[i]-pomoc1[i];

                    //f(k+1) = u(k) – q(k+1)
                    for(int i=0;i<nw;i++)
                        fp[i]=up[i]+qp[i];

                    //r(k+1) = r(k) – α(k)Af(k+1)
                    for (int i = 0; i < nw; i++)
                        pomoc1[i]=0;
                    for (int i = 0; i < nw; i++)
                        for (int j=0;j<nw;j++)
                            pomoc1[i] += h[i+1][j+1] * fp[j];
                    for (int i = 0; i < nw; i++)
                        pomoc1[i]=pomoc1[i]*alpha;
                    for (int i = 0; i < nw; i++)
                        rp[i] -= pomoc1[i];

                    //x(k+1) = x(k) + alpha*f(k+1)
                    for (int i = 0; i < nw; i++)
                        t4[i] += alpha*fp[i];

                    ropstare=rop;
                    for(int i=0;i<nw;i++)
                        ppstare[i]=pp[i];

                    rop=0;
                    for (int i = 0; i < nw; i++)
                        rop+=rp0[i]*rp[i];

                    ep=0;
                    for (int i = 0; i < nw; i++)
                        ep+=rp[i]*rp[i];
                }while(ep>zakres2);

                czas2[3]=System.currentTimeMillis(); 
                czaswykonania[3]=(czas2[3]-czas1[3]);
                czaswykonania[3]/=1000;
                System.out.println(czaswykonania[3]);            
                wynik4+="x\t"+"y\t"+"t"+"\n";
                for(int i=0;i<nw;i++)
                {
                    wynik4+=x[i+1]+"\t"+y[i+1]+"\t"+t4[i]+"\n";
                }  
            }

            System.out.println(wynik4);
            System.gc();

            czasallbez2=System.currentTimeMillis();
            czaswykonaniaallbez=(czasallbez2-czasallbez1);
            czaswykonaniaallbez/=1000;
            
            System.out.println("Czas bez komunikacij: "+czaswykonaniaallbez);
            
            zegarSerwer.increment();
            try 
            {
                zapis = new PrintWriter(new FileWriter(nazwaPliku, true));
            } catch (FileNotFoundException ex) {
                Logger.getLogger(Serwer.class.getName()).log(Level.SEVERE, null, ex);
            }
            zapis.println("Zegar: " + zegarSerwer.getLocalMax());
            zapis.println("Koniec obliczeń");

            if(rb==1)
            {
                daneWyjsciowe.czaswykonania=czaswykonania[0];
                daneWyjsciowe.wyniki=wynik1;
                zapis.println("Czas obliczeń: "+czaswykonania[0]);
            }
            if(rb==2)
            {
                daneWyjsciowe.czaswykonania=czaswykonania[1];
                daneWyjsciowe.wyniki=wynik2;
                zapis.println("Czas obliczeń: "+czaswykonania[1]);            
            }
            if(rb==3)
            {
                daneWyjsciowe.czaswykonania=czaswykonania[2];
                daneWyjsciowe.wyniki=wynik3;
                zapis.println("Czas obliczeń: "+czaswykonania[2]);            
            }
            if(rb==4)
            {
                daneWyjsciowe.czaswykonania=czaswykonania[3];
                daneWyjsciowe.wyniki=wynik4;
                zapis.println("Czas obliczeń: "+czaswykonania[3]);            
            }
            
            daneWyjsciowe.zegar=zegarSerwer.getLocalMax();
            oos.writeObject(daneWyjsciowe);
            oos.flush();
            
            czasall2=System.currentTimeMillis();
            czaswykonaniaall=(czasall2-czasall1);
            czaswykonaniaall/=1000;
            
            System.out.println("Czas z komunikacją: "+czaswykonaniaall);
                
                
            zegarSerwer.increment();
            zapis.println("Zegar: " + zegarSerwer.getLocalMax());
            zapis.println("Przesłałem wyniki");

            zapis.println();            
            zapis.println("Zegar: " + zegarSerwer.toString());
            zapis.println();
            zapis.println();


            zapis.close();  
                    
            ois.close();
            oos.close();
            clientSocket.close();
            if(czykoniec==1) break;   
        }
        serverSocket.close();
    }
}
